/** @file
  Brief description of the file's purpose.
  Detailed description of the file's contents and other useful
  information for a person viewing the file for the first time.
  Copyright (C) 2006 - 2023, Kunlun BIOS, Kunlun Technology (Beijing) Co., Ltd.. All
  Rights Reserved.
  You may not reproduce, distribute, publish, display, perform, modify, adapt,
  transmit, broadcast, present, recite, release, license or otherwise exploit
  any part of this publication in any form, by any means, without the prior
  written permission of Kunlun Technology (Beijing) Co., Ltd..
  @par Revision Reference:18
  - PI Version 1.0
  @par Glossary:
  - IETF - Internet Engineering Task Force
  - NASA - National Aeronautics and Space Administration
**/

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/KlSmbiosLib.h>
#include <Library/DevicePathLib.h>

#include <Protocol/PciIo.h>


/**
  Compare two device paths to check if they are exactly same.

  @param DevicePath1    A pointer to the first device path data structure.
  @param DevicePath2    A pointer to the second device path data structure.

  @retval TRUE    They are same.
  @retval FALSE   They are not same.

**/
STATIC
BOOLEAN
CompareDevicePath (
  IN EFI_DEVICE_PATH_PROTOCOL *DevicePath1,
  IN EFI_DEVICE_PATH_PROTOCOL *DevicePath2
  )
{
  UINTN Size1;
  UINTN Size2;

  Size1 = GetDevicePathSize (DevicePath1);
  Size2 = GetDevicePathSize (DevicePath2);

  if (Size1 != Size2) {
    return FALSE;
  }

  if (CompareMem (DevicePath1, DevicePath2, Size1) != 0) {
    return FALSE;
  }

  return TRUE;
}

/**
  This function is used to get PCI location from dp.

  @param  DevicePath      A pointer to the EFI device path protocol.
  @param  Seg             A pointer to the seg.
  @param  Bus             A pointer to the bus.
  @param  Dev             A pointer to the dev.
  @param  Func            A pointer to the func.

  @retval EFI_INVALID_PARAMETER
  @retval EFI_NOT_FOUND
  @retval EFI_SUCCESS

**/
STATIC
EFI_STATUS
GetPciLocationFromDp (
  IN  EFI_DEVICE_PATH_PROTOCOL   *DevicePath,
  OUT UINT8                      *Seg,
  OUT UINT8                      *Bus,
  OUT UINT8                      *Dev,
  OUT UINT8                      *Func
  )
{
  EFI_STATUS                Status;
  UINTN                     HandleCount;
  EFI_HANDLE                *HandleBuffer;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  UINTN                     Index;
  EFI_DEVICE_PATH_PROTOCOL  *PciDevicePath;
  UINTN                     PciSegment;
  UINTN                     PciBus;
  UINTN                     PciDevice;
  UINTN                     PciFunction;

  if (DevicePath == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if ( (Seg == NULL) || (Bus == NULL) || (Dev == NULL) || (Func == NULL) ) {
    return EFI_INVALID_PARAMETER;
  }
  HandleBuffer = NULL;
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiPciIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return EFI_NOT_FOUND;
  }

  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (HandleBuffer[Index], &gEfiDevicePathProtocolGuid, (VOID **) &PciDevicePath);
    if (EFI_ERROR (Status)) {
      continue;
    }
    Status = gBS->HandleProtocol (HandleBuffer[Index], &gEfiPciIoProtocolGuid, (VOID **) &PciIo);
    if (EFI_ERROR (Status)) {
      continue;
    }

    //DEBUG((EFI_D_ERROR, "%a(), Line:%d, DevicePath = %s\n", __FUNCTION__, __LINE__, ConvertDevicePathToText (PciDevicePath, FALSE, FALSE)));
    if (CompareDevicePath(DevicePath, PciDevicePath)) {
      PciIo->GetLocation (
                PciIo,
                &PciSegment,
                &PciBus,
                &PciDevice,
                &PciFunction
                );
      *Seg  = (UINT8)PciSegment;
      *Bus  = (UINT8)PciBus;
      *Dev  = (UINT8)PciDevice;
      *Func = (UINT8)PciFunction;
      FreePool(HandleBuffer);
      return EFI_SUCCESS;
    }
  }
  FreePool(HandleBuffer);
  return EFI_NOT_FOUND;
}
#if 0
STATIC struct {
  UINT8   Segment;
  UINT8   Bus;
  UINT8   Device;
  CHAR8   Function;
  CHAR8   *DevicePathStr;
} mFakePciDevs[] = {
  {  0, 1, 2, 0, "PciRoot(0x0)/Pci(0x0,0x0)"},
  {  0, 1, 3, 0, "PciRoot(0x1)/Pci(0x0,0x0)"},
  {  0, 1, 4, 0, "PciRoot(0x2)/Pci(0x0,0x0)"},
  {  0, 1, 5, 0, "PciRoot(0x4)/Pci(0x0,0x0)/Pci(0x10,0x0)/Pci(0x0,0x0)"},
  {  0, 1, 6, 0, "PciRoot(0x6)/Pci(0x0,0x0)"},
  {  0, 1, 7, 0, "PciRoot(0x7)/Pci(0x0,0x0)"},
  {  0, 1, 8, 0, "PciRoot(0x9)/Pci(0x0,0x0)"},
  {  0, 1, 9, 0, "PciRoot(0xA)/Pci(0x0,0x0)"},

  {  0, 5, 0, 0, "PciRoot(0x0)/Pci(0x4,0x0)/Pci(0x0,0x0)"},
  {  0, 6, 0, 0, "PciRoot(0x0)/Pci(0x5,0x0)/Pci(0x0,0x0)"},
  {  0, 3, 4, 0, "PciRoot(0x0)/Pci(0x3,0x0)"}
};
#endif
/**
  Brief description of Internal_Fake_GetPciLocationFromDpStr.

  @param  DevicePathStr
  @param  Seg
  @param  Bus
  @param  Dev
  @param  Func

  @retval EFI_SUCCESS   Function successful returned.
**/
EFI_STATUS
Internal_Fake_GetPciLocationFromDpStr (
  IN  CHAR8                *DevicePathStr,
  OUT UINT8                *Seg,
  OUT UINT8                *Bus,
  OUT UINT8                *Dev,
  OUT UINT8                *Func
  )
{
  //UINTN   Index = 0;

  //DEBUG((EFI_D_ERROR,"The device path is %a /n",mFakePciDevs[Index].DevicePathStr));   //[yfliu-0004]
#if 0

  for (Index = 0; Index < ARRAY_SIZE(mFakePciDevs); Index++) {
    if (0 == AsciiStrCmp (mFakePciDevs[Index].DevicePathStr, DevicePathStr)) {
      *Seg  = mFakePciDevs[Index].Segment;
      *Bus  = mFakePciDevs[Index].Bus;
      *Dev  = mFakePciDevs[Index].Device;
      *Func = mFakePciDevs[Index].Function;
      return EFI_SUCCESS;
    }
  }
#endif

  return EFI_NOT_FOUND;
}

/**
  This function is used to get Pci location from dp string.

  @param  DevicePathStr   A pointer to the device path string.
  @param  Seg             A pointer to the seg.
  @param  Bus             A pointer to the bus.
  @param  Dev             A pointer to the dev.
  @param  Func            A pointer to the func.

  @retval EFI_INVALID_PARAMETER
  @retval Status

**/
EFI_STATUS
EFIAPI
GetPciLocationFromDpStr (
  IN  CHAR8                *DevicePathStr,
  OUT UINT8                *Seg,
  OUT UINT8                *Bus,
  OUT UINT8                *Dev,
  OUT UINT8                *Func
  )
{
  EFI_STATUS                      Status;
  EFI_DEVICE_PATH_PROTOCOL        *DevicePath;
  //[yfliu-0004]
  UINTN                           UnicodeDpStrSize;
  CHAR16                          *UnicodeDpStr;
  
  UnicodeDpStrSize = 0;
  UnicodeDpStr = NULL;
  //[yfliu-0004]
  if ( (DevicePathStr == NULL) || (DevicePathStr[0] == '\0') ) {
    return EFI_INVALID_PARAMETER;
  }

  if ( (Seg == NULL) || (Bus == NULL) || (Dev == NULL) || (Func == NULL) ) {
    return EFI_INVALID_PARAMETER;
  }

  Status = Internal_Fake_GetPciLocationFromDpStr (DevicePathStr, Seg, Bus, Dev, Func);
  if (!EFI_ERROR (Status)) {
    return Status;
  }

  //[yfliu-0004]
  UnicodeDpStrSize = AsciiStrSize (DevicePathStr) * 2;
  UnicodeDpStr = AllocateZeroPool (UnicodeDpStrSize);
  UnicodeSPrint (UnicodeDpStr, UnicodeDpStrSize, L"%a", DevicePathStr);
  DevicePath = ConvertTextToDevicePath (UnicodeDpStr);
  //[yfliu-0004]
  Status = GetPciLocationFromDp (DevicePath, Seg, Bus, Dev, Func);
  if (DevicePath != NULL) {
    FreePool (DevicePath);
  }

  return Status;
}
